import pandas as pd
import numpy as np
import plotly.express as px
import matplotlib.pyplot as plt
import seaborn as sns
from scipy import stats
import warnings
import plotly.io as pio
png_renderer = pio.renderers["png"]
png_renderer.width = 500
png_renderer.height = 500
pio.renderers.default = "notebook"
#cargar dataset
path = './employee_attrition.csv'
df = pd.read_csv(path)
Las variables que influyen en el salario (de forma independiente) serán rankeadas según su correlación con dicha variable, y se descartarán aquellas que tienen relación directa, como por ejemplo MonthlyRate. Esto con el objetivo de tener una idea intuitiva de cómo es la entropía de estos datos. De igual forma, se realizará una mini investigación sobre cuales son las variables que podrían tener influencia directa en el sueldo.
df.head(3)
df.columns
df.info()
Las variables nominales serán pasadas a dummy para obtener una comparación numérica.
df_with_dummies = pd.get_dummies(df)
df.describe()
df_with_dummies.describe()
x = 'MonthlyIncome'
#Correlaciones
corrM = df_with_dummies.corr(method='spearman')
corr_MonthlyIncome = corrM[x]
corr_MonthlyIncome
Correlaciones negativas solo indican proporcionalidad inversa, por lo que se tomará el valor absoluto para visualizar mejor las variables que más correlación tienen con MontlyIncome.
abs(corr_MonthlyIncome).sort_values()
Se tienen entonces las correlaciones para este dataset con respecto a la variable objetivo. Notar que esta metodología puede caer fuertemente en sesgos si no se hace además un análisis cualitativo de las variables. Lo anterior debido a que la muestra presentada no necesariamente es representativa y sólo aplicarían para la empresa en particular de la cual se recolectaron los datos si ese fue el caso. De igual forma, correlaciones no lineales entre el sueldo y otras variables afectarían negativamente en la posición de dicha variable versus otras con correlaciones lineales.
Dicho lo anterior, se pueden ver resultados coincidentes con lo intuitivo. El nivel de trabajo es la variable que más correlación tiene con el sueldo, mientras que las siguientes variables tienen que ver bastante entre sí, ya que corresponden a medidas de tiempo, específicamente el número de años que lleva la persona en ciertas actividades (salvo por la edad); debido a esto, se deben además visualizar las correlaciones entre las otras variables, y de esta forma evitar la multicolinealidad exacta (variables con alta correlacion entre sí no son buenas para un modelo multivariable). Seguido de estas variables de tiempo, la correlación disminuye su valor ante variables como numCompaniesWorked y Education.
Por otro lado, con respecto a una pequeña revisión al respecto de las variables que afectan el sueldo, se pudo hallar información de distintas fuentes que afirman que el factor protagónico en el sueldo tiene que ver con el nivel de responsabilidad que requiere un puesto de trabajo, lo cual tiene que ver en este dataset con la variable 'JobLevel'. De la mano con lo anterior, las fuentes investigadas aseguran también que la edad tendría una relación con el sueldo de forma indirecta, ya que empleados con mayor experiencia son usualmente empleados con una edad más avanzada que quienes tienen poca experiencia [1][2]. Se afirma también que variables como el nivel educacional, el sexo y el estado civil afectan significativamente en el ingreso mensual[3]*. Finalmente, también se menciona que el nivel de demanda y competitividad existente para la ocupación del empleado afecta en el sueldo que este recibe (e.g. carreras de alta demanda tendrían un mayor sueldo que carreras menos demandadas en la industria) [2].
*El articulo de la cita [3] corresponde a un estudio hecho en china con el objetivo de buscar injusticias y discriminación en el sueldo según variables que debiesen estar uniformemente distribuídas, por lo que las conclusiones de este estuido no necesariamente sean ciertas para estos datos, pero sin embargo es necesario revisar dichas variables para detectar alguna injusticia en las distribuciones de sueldo.
[2] What Factors Affect Your Income?
A continuación se muestra la matriz de correlación entre variables. En ella se puede ver la alta correlación que tiene la variable JobLevel con Monthly Income; también se puede ver que las variables YearsAtCompany, YearsWithCurrManager, YearsSinceLastPromotion, YearsInCurrentRole y TotalWorkingYears tienen una alta correlación entre sí, por lo que para un modelo multivariable solo habría que usar una de estas variables. De igual forma, es necesario señalar que las variables descriptivas (las que se pasaron a dummy) no tienen una alta representatividad en la matriz de correlación debido a su carácter discreto (y específicamente binario o de pocas instancias).
## Revisión del mapa de correlaciones..
fig, ax = plt.subplots(figsize=(20,15))
sns.heatmap(corrM, ax = ax,
xticklabels=corrM.columns,
yticklabels=corrM.columns,center=0.15);
ax.set_title('Minecraft Sword PixelArt (Matriz de Correlaciones, incluye variables descriptivas transformadas a dummy)');
Dado lo anterior entonces, se eligen las siguientes variables como prioridad a analizar:
Evalúe la necesidad de normalizar/estandarizar los datos o transformar las variables (por ejemplo a dummy o aplicar logaritmo).
En la parte anterior, se pasaron a dummy todas las variables descriptivas con el objetivo de poder trabajarlas numéricamente (debido a su propia naturaleza, no se podían tratar numéricamente sin este ajsute) . A continuación se harán distintas visualizaciones que permitirán decidir qué hacer con las variables seleccionadas.
variables_de_interes_continuas = ['MonthlyIncome', 'TotalWorkingYears', 'Age']
variables_de_interes_descriptivas = ['JobLevel', 'Education', 'Gender', 'JobRole']
for variable in variables_de_interes_continuas:
fig, ax = plt.subplots(figsize=(15,8))
sns.distplot(df[variable], kde = False ,fit=stats.norm)
ax.set_title(f'\n\nHistograma de {variable} y su ajuste normal')
De las variables numéricas graficadas aquí, es necesario sacar logaritmo de TotalWorkingYears y de MonthlyIncome, debido a su caracteristica distribución inclinada a la izquierda. De igual forma se hará con la variable Age, pues su ajuste normal también podría mejorar con logaritmo.
df_ajustado = pd.DataFrame()
df_ajustado['MonthlyIncome'] = df['MonthlyIncome']
df_ajustado['TotalWorkingYears'] = df['TotalWorkingYears']
df_ajustado['Age'] = df['Age']
df_ajustado['LogMonthlyIncome'] = np.log(df['MonthlyIncome']+1)
df_ajustado['LogTotalWorkingYears'] = np.log(df['TotalWorkingYears']+1)
df_ajustado['LogAge'] = np.log(df['Age']+1)
variables_df_ajustado = ['LogMonthlyIncome', 'LogTotalWorkingYears', 'LogAge']
for variable in variables_df_ajustado:
fig, ax = plt.subplots(figsize=(15,8))
sns.distplot(df_ajustado[variable], kde = False ,fit=stats.norm)
ax.set_title(f'\n\nHistograma de {variable} y su ajuste normal')
La transformación logarítmica de los datos permite un mejor ajuste normal. Lo cual es conveniente para la regresión lineal pues disminuye el efecto de los sesgos [4].
Realice graficos para ver la relacion entre las variables independientes con la dependiente.
NOTA: Se trabajará con los datos originales para de esta forma apoyar o poner en duda el uso de las estrategias anteriormente propuestas.
A continuación se harán scatter plots coloreados según alguna variable descriptiva.
Como puede verse, JobLevel divide de forma casi perfecta a ciertos grupos en las variables de tiempo y de ingreso mensual. JobRole también genera algunos 'clusters' de mayores ingresos y edad (Director y Manager por ejemplo). Gender a simple vista no genera división en sueldo y edad, salvo algunos valores outliers que favorecen a los hombres en cierta medida. De igual forma, se puede ver una tendencia con clara intención de proporcionalidad directa, pero muy dispersa en cuanto a la variable age y totalWorkingYears con respecto a MonthlyIncome, lo anterior debido a que no se ve un limite de edad para sueldos bajos, pero sí se ve una limitación en los sueldos altos y la edad, es decir, muy poca gente joven tiene sueldos altos, pero la gente mayor o con más experiencia tiene una amplia distribución de sueldo (en promedio mayor, pero con alta desviación).
variables_de_interes = ['MonthlyIncome', 'TotalWorkingYears', 'Age', 'JobLevel', 'Education', 'Gender', 'JobRole']
df_2 =df.replace({'Education':{1: 'Below College', 2: 'College', 3: 'Bachelor', 4: 'Master', 5: 'Doctor'}})
df_2 =df_2.replace({'JobLevel':{1: 'Muy Bajo', 2: 'Bajo', 3: 'Medio', 4: 'Alto', 5: 'Muy Alto'}})
for nominal in variables_de_interes_descriptivas:
fig = px.scatter_matrix(df_2[variables_de_interes], dimensions=variables_de_interes[:3] ,color = nominal)
fig.update_layout(
title=f'Scatter Plots para las variables ordinales y continuas, separado según {str(nominal)} ',
dragmode='select',
width=800,
height=800,
hovermode='closest',
)
fig.show()
A continuación se realizan boxplots que analizan diversas combinaciones de las variables descriptivas con la variable objetivo (MonthlyIncome). Estos aumentan la intuición de que no hay sesgo de género. Aquí puede verse con mayor claridad el cómo JobLevel y JobRole generan grupos de gente con sueldos absolutamente diferentes. Se puede ver además que la educación no está tan relacionada con el sueldo como se podría creer popularmente.
fig = px.box(df_2, 'Gender', 'MonthlyIncome', color = 'Education', title='Caracterización de los Trabajadores según Edad, Género y Nivel Educacional.', points='all')
fig.update_layout(
title='BoxPlots para variables descriptivas vs continuas: Education ',
dragmode='select',
width=1300,
height=500,
hovermode='closest',
)
fig.show()
fig = px.box(df_2, 'Gender', 'MonthlyIncome', color = 'JobRole', title='Caracterización de los Trabajadores según Edad, Género y Nivel Educacional.', points='all')
fig.update_layout(
title='BoxPlots para variables descriptivas vs continuas: JobRole ',
dragmode='select',
width=1300,
height=500,
hovermode='closest',
)
fig.show()
fig = px.box(df_2, 'Gender', 'MonthlyIncome', color = 'JobLevel', title='Caracterización de los Trabajadores según Edad, Género y Nivel Educacional.', points='all')
fig.update_layout(
title='BoxPlots para variables descriptivas vs continuas: JobLevel',
dragmode='select',
width=1300,
height=500,
hovermode='closest',
)
fig.show()
variables_de_interes = ['MonthlyIncome', 'TotalWorkingYears', 'Age', 'JobLevel', 'Education', 'Gender', 'JobRole']
df_var = pd.get_dummies(df[variables_de_interes])
corrM = df_var.corr(method = 'spearman')
fig, ax = plt.subplots(figsize=(10,8))
sns.heatmap(corrM, ax = ax,
xticklabels=corrM.columns,
yticklabels=corrM.columns,center=0.0);
ax.set_title('Minecraft Netherack PixelArt (Matriz de Correlaciones, incluye variables descriptivas transformadas a dummy)');
Finalmente, con base a todo lo de las preguntas anteriores, se llega a la conclusión que, de las variables seleccionadas, las que tienen mayor valor al momento de explicar el sueldo son (ordenadas de mayor a menor):
Mientras que las variables Age, Gender y Education tienen menor influencia en la variable objetivo, contrario a lo que anteriormente se propuso. Específicamente, Age tiene una alta correlación con TotalWorkingYears, por lo que un modelo con estas dos variables no sería óptimo.
De igual forma, JobLevel y TotalWorkingYears tienen una alta correlación entre sí, por lo que solo quedarían las variables de JobLevel y JobRole como candidatos ideales para explicar el sueldo. Lamentablemente, dado que se trata de variables discretas y de pocas instancias, estas dos variables por sí solas generarán predicciones discretas, por lo que es fundamental tener cuidado al trabajar con ellas. Igualmente, entre ciertos roles de trabajo existen correlaciones relativamente altas con JobLevel, por lo que tampoco sería ideal usar estas variables en conjunto. La correlación de TotalWorkingYears con JobLevel se puede solucionar utilizando la variable Age con JobLevel, la cual tiene una correlación más baja con esta variable y suficientemente alta con MonthlyIncome como para ser considerada.
Con respecto a las variables dummy de JobRole; las variables human resources, manager, laboratory tecnician y research scientist son las que más correlación tienen con la variable objetivo. Estas variables tienen una baja correlación entre sí, lo que no es malo para el modelo si es que se llegan a usar juntas como es de esperarse.
Se experimentará inicialmente con todas las variables planteadas, y sin preprocesar (sin aplicar logaritmo). El modelo resultante debiese funcionar, aunque no de forma óptima.
variables_de_interes = ['MonthlyIncome', 'JobLevel', 'Age','Education' ,'JobRole', 'Gender']
variables_continua = variables_de_interes[:3]
variables_a_dummy = variables_de_interes[3:]
df_m1 = pd.DataFrame()
for continua in variables_continua:
df_m1[continua] = df[continua]
for variable in variables_a_dummy:
for dummy in df_with_dummies.columns:
if variable in dummy and 'EducationF' not in dummy:
df_m1[dummy] = df_with_dummies[dummy]
df_m1.describe()
df_m1.info()
import statsmodels.api as sm
v = [a for a in df_m1.columns[:14]] #se usa solo un genero
y =df_m1[v[0]]
X = df_m1[v[1:]]
exog = sm.add_constant(X)
model = sm.OLS(y, exog).fit()
predictions = model.predict(exog, True)
model.summary()
Se obtiene un de 0.942 y un ajustado de 0.941, Las variables más significativas son JobLevel y JobRole, mientras que Education no tiene una significancia estadística suficientemente fuerte para aceptar la hipótesis de que afectan en la variable MonthlyIncome.
Notar que const corresponde a
El estadístico F permite inferir con alta confianza** que sí existe una relación entre las variables entregadas y la variable objetivo. (no se rechaza la hipótesis que afecan en el modelo, de forma general).
*Se tomaron las variables más significativas como las de menor p-valor [referencia].
**Pese a la ambiguedad de la frase, una confianza alta la definiremos como superior al 95%
El resumen del modelo permite observar un alto valor de , lo cual es sospechosamente bueno para el modelo. Es posible notar cómo las variables dummy tienen distintos signos en el valor de , esto es debido a la misma correlación entre dicha variable y el sueldo, en efecto, se puede ver cómo los JobRole con menor sueldo son severamente penalizados, en el sueldo, mientras que los JobRole con mayor.
La verosimilitud da extremadamente baja, junto a ella, los valores AIC/BIC dan extremadamente altos [ver referencia], lo que indica que este modelo solo funcionaría para los datos de ejemplo con los que fue ajustado (es decir, está sobreajustado), y por lo tanto no es de ninguna forma un buen modelo.
A continuación se muestran algunos gráficos que permiten ver este sobreajuste del modelo, el cual incluso se ajusta a puntos que pueden ser considerados como 'Outliers'. La presencia de variables discretas como JobLevel generan una fuerte discretización de las
La función misma sugiere un problema de Multicolinealidad, el cual no estaría relacionado con las variables elegidas, sino que con el parámetro de error añadido (definido en exog en el código al hacer add_constant), y es debido a problemas de escalamiento propio de la función [issue]. (esto pues, sin el parámetro constante no genera esta advertencia)
Con respecto a la endogenidad en el modelo, es necesario hacer un profundo análisis. Tratar con este sesgo no es algo trivial [referencia], y puede deberse a variables faltantes en el modelo, o bien por autoregresión con autocorrelacion de errores [Wikipedia]), [Foro Académico]. Especificamente en este modelo no aparenta haber endogenidad debido a que el ajuste obtenido es bueno, sin embargo, se requieren más estudios al respecto.
from statsmodels.sandbox.regression.predstd import wls_prediction_std
prstd, iv_l, iv_u = wls_prediction_std(model)
for column in X.columns:
fig, ax = plt.subplots(figsize=(8,6))
ax.plot(X[column], y, 'bo', label="True")
ax.plot(X[column], predictions, 'r.', label="data")
ax.set_title(f'Predicciones del modelo contrastadas con los valores reales')
ax.set_xlabel(f'{column}')
ax.set_ylabel('MonthlyIncome')
ax.legend(('Reales', 'Predicciones'))
plt.show()
A continuación se agregan las transformaciones respectivas y se seleccionan los parámetros.
df_ajustado = pd.DataFrame()
df_ajustado['LogMonthlyIncome'] = np.log(df['MonthlyIncome']+1)
df_ajustado['LogTotalWorkingYears'] = np.log(df['TotalWorkingYears']+1)
df_ajustado['LogAge'] = np.log(df['Age']+1)
df_ajustado['JobLevel'] = df['JobLevel']
df_ajustado['Education'] = df['Education']
for dummy in df_with_dummies:
if 'JobRole' in dummy:
df_ajustado[dummy] = df_with_dummies[dummy]
for dummy in df_with_dummies:
if 'Gender' in dummy:
df_ajustado[dummy] = df_with_dummies[dummy]
df_ajustado.describe()
df_ajustado.info()
corrM = df_ajustado.corr(method = 'pearson')
fig, ax = plt.subplots(figsize=(10,8))
sns.heatmap(corrM, ax = ax,
xticklabels=corrM.columns,
yticklabels=corrM.columns,center=0.0);
ax.set_title('Minecraft Trident or AymaraFlag PixelArt (Matriz de Correlaciones, dataframe ajustado)');
Nuevamente no se pueden usar JobLevel con TotalWorkingYears juntos
import statsmodels.api as sm
v = ['LogMonthlyIncome', 'LogAge', 'JobLevel',
'Education', 'JobRole_Healthcare Representative',
'JobRole_Human Resources', 'JobRole_Laboratory Technician',
'JobRole_Manager', 'JobRole_Manufacturing Director',
'JobRole_Research Director', 'JobRole_Research Scientist',
'JobRole_Sales Executive', 'JobRole_Sales Representative',
'Gender_Female']
#'JobRole_Sales Representative']
y = df_ajustado[v[0]]
X = df_ajustado[v[1:]]
exog = sm.add_constant(X)
model = sm.OLS(y, exog).fit()
predictions = model.predict(exog)
model.summary()
Se obtiene un de 0.877 y un ajustado de 0.876, Las variables más significativas son JobLevel y JobRole, mientras que Education y Gender no tienen una significancia estadística suficientemente fuerte para inferir que afectan en la variable MonthlyIncome. Tal como era previsto en el análisis de datos. En posteriores modelos no se incluirán estas variables (se rechaza la hipótesis que afectan en el sueldo).
Notar que const corresponde a
El estadístico F permite inferir con alta confianza que sí existe una relación entre las variables entregadas y la variable objetivo. (no se rechaza la hipótesis que afecten en el modelo)
El valor de baja en este modelo, pero sin embargo resulta ser un mejor modelo que el anterior, por el hecho que el sobreajuste es menor y en consecuencia el sesgo también lo es. Lo anterior se nota incluso visualmente en los scatter plot de abajo. Como puede verse, a pesar que la estratificación por JobLevel es fuertemente notoria, el logaritmo ha hecho que los outliers estén más juntos y por lo tanto disminuye el sesgo del modelo.
De igual forma, el valor de no es para nada bajo (relativamente hablando).
Nuevamente la advertencia de multicolinealidad se debe al valor const.
prstd, iv_l, iv_u = wls_prediction_std(model)
for column in X.columns:
fig, ax = plt.subplots(figsize=(8,6))
ax.plot(X[column], y, 'bo', label="True")
ax.plot(X[column], predictions, 'r.', label="data")
ax.set_title(f'Predicciones del modelo contrastadas con los valores reales')
ax.set_xlabel(f'{column}')
ax.set_ylabel('LogMonthlyIncome')
ax.legend(('Reales', 'Predicciones'))
plt.show()
Dado que la variable JobLevel está discretizando los datos predichos por el modelo, se reemplazará por TotalWorkingYears, la cual no ha sido usada debido a su alta correlación con JobLevel. Para evitar sesgos y sobreajustes, se hará un recorte de datos según los valores anómalos en el MonthlyIncome. Con esto se espera obtener un mejor ajuste que en los modelos anteriores
Se hará la cota inferior y superior según 1.5 x rango intercuartílico [fuente]
from scipy.stats import iqr
def IQR(df_ajustado, variable):
desc = df_ajustado[variable].describe()
q1 = desc['25%']
q2 = desc['50%']
q3 = desc['75%']
qri = iqr(df_ajustado[variable])
cota_inf = q1 - (qri * 1.5)
cota_sup = q3 + (qri * 1.5)
df_ajustado = df_ajustado[df_ajustado[variable] > cota_inf ]
df_ajustado = df_ajustado[df_ajustado[variable] < cota_sup ]
return df_ajustado
De las tres variables numéricas solo LogTotalWorkingYears tenía outliers incluso con logaritmo, los cuales se limplian a continuación. Posterior a remover los outliers de esta variable las otras seguían sin outliers.
df_limpio = IQR(df_ajustado, 'LogTotalWorkingYears')
df_limpio.describe()
Los dummy se agregarán en una unica columna, con codificación numérica para cada JobRole con el objetivo de reducir la dimensionalidad.
df_limpio['JobRole'] = 6*df_limpio['JobRole_Healthcare Representative'] + 3*df_limpio['JobRole_Human Resources'] +2* df_limpio['JobRole_Laboratory Technician'] + 8* df_limpio['JobRole_Manager'] + 5* df_limpio['JobRole_Manufacturing Director'] + 7* df_limpio['JobRole_Research Director'] + 1* df_limpio['JobRole_Research Scientist'] + 4* df_limpio['JobRole_Sales Executive'] + 0* df_limpio['JobRole_Sales Representative']
import statsmodels.api as sm
v = ['LogMonthlyIncome', 'LogTotalWorkingYears','JobRole'
]
#'JobRole_Sales Representative']
y = df_limpio[v[0]]
X = df_limpio[v[1:]]
exog = sm.add_constant(X)
model = sm.OLS(y, exog).fit()
predictions = model.predict(exog)
model.summary()
Se obtiene un R2 de 0.768 y un R2 ajustado de 0.768, el estadístico F, nuevamente permite inferir con alta confianza que sí existe una relación entre las variables entregadas y la variable objetivo. (no se rechaza la hipótesis que afecten en el modelo). El p-valor de las variables usadas es tal que permite no rechazar la hipótesis nula que no afectan en el sueldo (i.e. que ).
El modelo con LogTotalWorkingYears funciona aún peor que el modelo N°2. El valor de ya no es tan alto. Esto debido a que no considerar la variable JobLevel es en realidad un error, pues es la variable de principal valor en este dataset con respecto al sueldo, y generaría un sesgo de endogenidad. Con respecto a la discretización de las predicciones, a continuación puede verse que funciona peor que el modelo anterior también, debido a que JobRole es también una variable discreta de pocas instancias.
La reducción de dimensionalidad no generó cambios significativos más que el ahorro de memoria, en efecto, esto es trivial ya que simplemente es otra forma de codificar lo mismo.
prstd, iv_l, iv_u = wls_prediction_std(model)
for column in X.columns:
fig, ax = plt.subplots(figsize=(8,6))
ax.plot(X[column], y, 'bo', label="True")
ax.plot(X[column], predictions, 'r.', label="data")
ax.set_title(f'Predicciones del modelo contrastadas con los valores reales')
ax.set_xlabel(f'{column}')
ax.set_ylabel('LogMonthlyIncome')
ax.legend(('Reales', 'Predicciones'))
plt.show()
A modo netamente experimental, se trata de rebobinar el criterio de variables anteriormente elegidas, y probar las mismas del modelo 2, pero con los arreglos del modelo 3...
import statsmodels.api as sm
v = ['LogMonthlyIncome', 'LogAge', 'JobLevel','JobRole']
#'JobRole_Sales Representative']
y = df_limpio[v[0]]
X = df_limpio[v[1:]]
exog = sm.add_constant(X)
model = sm.OLS(y, exog).fit()
predictions = model.predict(exog)
model.summary()
Finalmente en este modelo se obtienen resultados incluso mejores que en el modelo 2, esto pues, no se están considerando las variables de poco valor como el género o la educación. Incluso visualmente pueden verse mejoras en los scatter plot (A pesar que se genera una nube de predicciones que sobrevalora demasiado el último JobLevel). Este sería el mejor modelo que se pudo encontrar, dado su mayor y alta verosimilitud. El p-valor de LogAge podría generar dudas si es que esta variable tiene un valor significativo para este modelo.
prstd, iv_l, iv_u = wls_prediction_std(model)
for column in X.columns:
fig, ax = plt.subplots(figsize=(8,6))
ax.plot(X[column], y, 'bo', label="True")
ax.plot(X[column], predictions, 'r.', label="data")
ax.set_title(f'Predicciones del modelo contrastadas con los valores reales')
ax.set_xlabel(f'{column}')
ax.set_ylabel('LogMonthlyIncome')
ax.legend(('Reales', 'Predicciones'))
plt.show()
Esto se explica en detalle en cada modelo, pero a nivel general, los resultados están fuertemente ligados al hecho que se modeló con variables nominales y que estas fueran las de mayor valor en el dataset. Para evitar esta suerte de 'discretización' en las predicciones, se sospecha que faltó añadir más variables en el modelo, que compensaran el efecto de las nominales. Aún así, en todos los modelos no se rechazó la hipótesis nula de que las variables no afectaran en el sueldo, lo que es favorable a las conclusiones hechas en la parte 1.
Si, en el modelo 3 es donde más sospechas hay de endogenidad por falta de variables, en efecto, debido a que no se incluyó la variable de mayor valor y se sustituyó por una de menor correlación con la variable objetivo pero con alta correlación con la variable JobLevel.
Tal como se dijo en párrafos anteriores, se dice que no es trivial tratar con el sesgo de endogenidad pero que sin embargo este generaría grandes catástrofes en un modelamiento serio y completo, por lo que se requieren mayores investigaciones para confirmar o no si el paupérrimo desempeño de algunos modelos es debido parcial o totalmente al sesgo de endogenidad.
A priori, podría existir endogenidad en este trabajo por la no inclusión de variables necesarias, vinculandolo con lo dicho en la P1, añadir variables que amortiguen en carácter discreto de las predicciones mejoraría sustancialmente el resultado, por lo que en ese sentido existiría un sesgo de endogenidad por falta de variables.